﻿using System;
using System.Collections.Generic;

namespace TimetableCSP.Scheduler.Entities
{
	/// <summary>
	/// This is a base class for variables in a constraint satisfaction problem.
	/// </summary>
	public abstract class AbstractVariable : ICloneable
	{
		protected SortedList<string, ValueCeilingPair> attributes = new SortedList<string, ValueCeilingPair>();
		private bool pinned = false; 
		private int id;

		/// <summary>
		/// Gets or sets the identifier of the variable.
		/// </summary>
		/// <value>
		/// The identifier.
		/// </value>
		public int Id
		{
			get { return id; }
			set { id = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="AbstractVariable"/> is pinned.
		/// </summary>
		/// <value>
		///   <c>true</c> if pinned; otherwise, <c>false</c>.
		/// </value>
		/// <remarks>
		/// A pinned variable will not be modified in any way when searching for a valid solution
		/// </remarks>
		public bool Pinned
		{
			get { return pinned; }
			set { pinned = value; }
		}

		protected class ValueCeilingPair
		{
			public int Value = -1;
			public int Ceiling = -1;
		}

		/// <summary>
		/// A variable can have one or more attributes (dimensions). This method returns a list of their names.
		/// </summary>
		/// <returns>A list of attribute names.</returns>
		public abstract IEnumerable<string> GetAttributeNames();

		/// <summary>
		/// Initializes a new instance of the <see cref="AbstractVariable"/> class.
		/// </summary>
		public AbstractVariable()
		{
			CreateAttributes();
		}

		/// <summary>
		/// Gets or sets the <see cref="System.Int32"/> with the specified attribute name.
		/// </summary>
		public int this[string attributeName]
		{
			get
			{
				return this.attributes[attributeName].Value;
			}
			set
			{
				if (!this.attributes.ContainsKey(attributeName))
				{
					this.attributes.Add(attributeName, new ValueCeilingPair());
				}
				this.attributes[attributeName].Value = value;
			}
		}

		/// <summary>
		/// Gets the domain ceiling for the specified attribute.
		/// </summary>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <returns>The domain ceiling.</returns>
		/// <remarks>It's assumed that the floor of all domains is 0.</remarks>
		public int GetDomainCeiling(string attributeName)
		{
			return this.attributes[attributeName].Ceiling;
		}

		/// <summary>
		/// Sets the domain ceiling for the specified attribute.
		/// </summary>
		/// <param name="attributeName">Name of the attribute.</param>
		/// <param name="ceiling">The domain ceiling.</param>
		/// <remarks>It's assumed that the floor of all domains is 0.</remarks>
		public void SetDomainCeiling(string attributeName, int ceiling)
		{
			if (!this.attributes.ContainsKey(attributeName))
			{
				this.attributes.Add(attributeName, new ValueCeilingPair());
			}
			this.attributes[attributeName].Ceiling = ceiling;
		}

		#region Abstract methods
		/// <summary>
		/// Constructs and initializes the variable's attributes.
		/// </summary>
		protected abstract void CreateAttributes();

		/// <summary>
		/// Creates a new object that is a copy of the current instance.
		/// </summary>
		/// <returns>
		/// A new object that is a copy of this instance.
		/// </returns>
		public abstract object Clone();
		#endregion
	}
}
